/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2008-2011, Salzburg Research. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * - Neither the name of the KiWi Project nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * Contributor(s):
 * sschaffe
 *
 * File Info:
 * User: sschaffe
 * Date: 2011/01/12
 * Time: 10:15
 *
 * Project: KiWi2, kiwi-core module
 */

package kiwi.social.services.tagging;

import kiwi.core.api.config.ConfigurationService;
import kiwi.core.api.facading.FacadingService;
import kiwi.core.api.triplestore.ResourceService;
import kiwi.core.api.triplestore.TripleStore;
import kiwi.core.api.user.UserService;
import kiwi.core.exception.NamespaceResolvingException;
import kiwi.core.exception.ResolvingException;
import kiwi.core.model.Constants;
import kiwi.core.model.rdf.KiWiNode;
import kiwi.core.model.rdf.KiWiResource;
import kiwi.core.model.rdf.KiWiTriple;
import kiwi.core.model.rdf.KiWiUriResource;
import kiwi.core.model.user.KiWiUser;
import kiwi.social.api.tagging.TaggingService;
import kiwi.social.model.tagging.Tag;
import kiwi.social.model.tagging.Tagging;
import org.slf4j.Logger;

import javax.inject.Inject;
import java.util.*;

/**
 * Add file description here!
 * <p/>
 * User: sschaffe
 */
public class TaggingServiceImpl implements TaggingService {

    @Inject
    private Logger log;

    @Inject
    private ConfigurationService configurationService;

    @Inject
    private ResourceService resourceService;


    @Inject
    private TripleStore tripleStore;


    @Inject
    private FacadingService facadingService;


    @Inject
    private UserService userService;

    /**
     * Create a new tagging of taggedItem with taggingItem based on the given label for taggingUser.
     * This method creates a new tag instance with a generated, unique uri resource. The tag is then
     * persisted in the entityManager and tripleStore, and returned to the caller.
     *
     * @param label       a textual label of the tagging; this is usually the string entered by the user
     *                    which has then been used to look up taggingItem
     * @param taggedItem  the tagged content item
     * @param taggingItem the content item used as tag
     * @param taggingUser the user who performed the tagging
     * @return the newly created and persisted tag
     */
    @Override
    public Tagging createTagging(String label, KiWiResource taggedItem, KiWiResource taggingItem, KiWiUser taggingUser) {
        KiWiResource taggingResource = resourceService.createUriResource(configurationService.getBaseUri()+"resource/"+ UUID.randomUUID().toString());

        Tagging tagging = facadingService.createFacade(taggingResource,Tagging.class);
        Tag tag         = facadingService.createFacade(taggingItem,Tag.class);

        tagging.setTaggedResource(taggedItem);
        tagging.setTaggingResource(tag);
        tagging.setTaggedBy(taggingUser.getResource());
        tagging.setCreatedOn(new Date());

        // TODO: should rather be inferred by the reasoner!
        tripleStore.createTriple(taggedItem,resourceService.createUriResource(Constants.NS_HGTAGS+"taggedWithTag"),taggingItem, null);
        //resourceService.addType(taggedItem,resourceService.createUriResource(Constants.NS_KIWI_CORE + "ContentItem"));

        return tagging;
    }

    /**
     * List the taggings associated with a content item. This method is safer than getTags of
     * ContentItem, because it avoids the detached entity problem...
     *
     * @param resource
     * @return
     */
    @Override
    public List<Tagging> getTaggingsByItem(KiWiResource resource) {
        List<Tagging> result = new LinkedList<Tagging>();
        try {

            for(KiWiResource tagging : resourceService.listIncomingNodes(resource, Constants.NS_HGTAGS+"taggedResource")) {
                result.add(facadingService.createFacade(tagging,Tagging.class));
            }

        } catch (ResolvingException e) {
            log.error("could not resolve HGTAGS namespace; this error indicates a programming bug and should never happen");

        }

        if(log.isDebugEnabled())
            log.debug("getTaggingsByItem({}): {} results",resource.toString(), result.size());

        return result;
    }

    /**
     * Return tags by a given user
     *
     * @param currentUser
     * @return
     */
    @Override
    public List<Tagging> getTaggingsByUser(KiWiUser currentUser) {
        List<Tagging> result = new LinkedList<Tagging>();
        try {

            for(KiWiResource tagging : resourceService.listIncomingNodes(currentUser.getResource(), Constants.NS_HGTAGS+"taggedBy")) {
                result.add(facadingService.createFacade(tagging,Tagging.class));
            }

        } catch (ResolvingException e) {
            log.error("could not resolve HGTAGS namespace; this error indicates a programming bug and should never happen");

        }

        return result;
    }

    /**
     * Remove an existing tagging from the database.
     *
     * @param tagging the tagging to remove
     */
    @Override
    public void removeTagging(KiWiResource tagging) {

        KiWiResource taggedItem  = null;
        KiWiResource taggingItem = null;

        Iterator<KiWiNode> taggedItems = null, taggingItems = null;
        try {
            taggedItems = resourceService.listOutgoingNodes(tagging, Constants.NS_HGTAGS+"taggedResource").iterator();
            if(taggedItems.hasNext()) {
                taggedItem = (KiWiResource)taggedItems.next();
            }

            taggingItems = resourceService.listOutgoingNodes(tagging, Constants.NS_HGTAGS+"taggingResource").iterator();
            if(taggingItems.hasNext()) {
                 taggingItem = (KiWiResource)taggingItems.next();
             }
        } catch (ResolvingException e) {
            log.error("could not resolve HGTAGS namespace; this error indicates a programming bug and should never happen");
        }

        if(taggedItem != null && taggedItem != null) {
            tripleStore.removeTriple(taggedItem,resourceService.getUriResource(Constants.NS_HGTAGS+"taggedWithTag"),taggingItem, null);
        }

        resourceService.removeResource(tagging);
    }


    public Tagging createTagging(KiWiResource item, String label, KiWiUser user) {
        // find all matching tag candidates
        Collection<KiWiResource> tagCandidates = resourceService.listResourcesByProperty(Constants.NS_HGTAGS+"name",label);

        if(tagCandidates.size() == 1) {
            // exactly one match, we create a tagging
            KiWiResource tag = tagCandidates.iterator().next();

            return createTagging(label,item,tag,user);
        } else {
            KiWiResource tag = resourceService.createUriResource(configurationService.getBaseUri()+"resource/"+ UUID.randomUUID().toString());
            Tag f_tag = facadingService.createFacade(tag,Tag.class);
            f_tag.setTagLabels(Collections.singleton(label));

            return createTagging(label,item,tag,user);
        }


    }

    /**
     * Create taggings for a contentItem <code>item</code> with the taggingContentItem
     * having the label <code>label</code>. If a contentItem with the label already exists,
     * it will be reused, otherwise freshly created.
     *
     * @param item
     * @author szabyg
     */
    @Override
    public void addTags(KiWiResource item, String[] tagLabels, KiWiUser user) {
        for(String label : tagLabels) {
            createTagging(item, label, user);
        }

    }

    /**
     * @param item
     * @param tagLabel
     * @param user
     */
    @Override
    public void removeTagging(KiWiResource item, String tagLabel, KiWiUser user) {
        // first find the tagging of the user with the given label
        // TODO: we should use SPARQL here!
        try {

            for(KiWiResource r : resourceService.listIncomingNodes(item,Constants.NS_HGTAGS+"taggedResource")) {
                Tagging tagging = facadingService.createFacade(r,Tagging.class);

                if(tagging.getTaggingResource().getTagLabels().contains(tagLabel) &&
                   (user == null || tagging.getTaggedBy().equals(user.getResource()))) {
                    removeTagging(tagging.getDelegate());
                }
            }



        } catch (ResolvingException e) {
            log.error("an error occurred while resolving the HGTags namespace; this should not happen and is a programming error.");
        }
    }



    @Override
    public void removeTags(KiWiResource item, String[] tagLabels, KiWiUser user) {
        for(String tagLabel : tagLabels) {
            removeTagging(item, tagLabel, user);
        }
    }

    /**
     * Verify whether the given label has been associated with the content item.
     *
     * @param taggedItem the content item that has been tagged
     * @param label      the label used for tagging
     * @return true if the label was used for tagging the item.
     */
    @Override
    public boolean hasTag(KiWiResource taggedItem, String label) {
        // TODO: we should use SPARQL here!

        try {
            for(KiWiResource r : resourceService.listIncomingNodes(taggedItem,Constants.NS_HGTAGS+"taggedResource")) {
                Tagging tagging = facadingService.createFacade(r,Tagging.class);

                if(tagging.getTaggingResource().getTagLabels().contains(label)) {
                    return true;
                }
            }
        } catch (ResolvingException e) {
            log.error("an error occurred while resolving the HGTags namespace; this should not happen and is a programming error.");
        }

        return false;
    }

    /**
     * Return all tags stored in the KiWi system.
     *
     * @return
     */
    @Override
    public List<Tag> getAllTags() {
        KiWiResource type = resourceService.createUriResource(Constants.NS_HGTAGS + "Tag");

        return new LinkedList<Tag>(facadingService.createFacade(resourceService.listResources(type),Tag.class));
    }

    /**
     * Return all tags that are not SKOS concepts within a hierarchy (i.e. have neither broader nor narrower concepts)
     *
     * @return
     */
    @Override
    public List<Tag> getFreeTags() {
        // TODO: use SPARQL

        KiWiResource type = resourceService.createUriResource(Constants.NS_HGTAGS + "Tag");
        List<Tag> result = new LinkedList<Tag>();

        try {

            for(KiWiResource tag : resourceService.listResources(type)) {
                List<KiWiTriple> incoming = resourceService.listIncoming(tag,Constants.NS_SKOS+"broader");
                List<KiWiTriple> outgoing = resourceService.listOutgoing(tag, Constants.NS_SKOS + "broader");

                if(incoming.size() == 0 && outgoing.size() == 0) {
                    result.add(facadingService.createFacade(tag,Tag.class));
                }
            }


        } catch(ResolvingException ex) {
            log.error("an error occurred while resolving the SKOS namespace; this should not happen and is a programming error.");
        }

        return result;
    }

    /**
     * Return all tags that are SKOS concepts within a hierarchy (i.e. have either broader or narrower concepts)
     *
     * @return
     */
    @Override
    public List<Tag> getControlledTags() {
        // TODO: use SPARQL

        KiWiResource type = resourceService.createUriResource(Constants.NS_HGTAGS + "Tag");
        List<Tag> result = new LinkedList<Tag>();

        try {

            for(KiWiResource tag : resourceService.listResources(type)) {
                List<KiWiTriple> incoming = resourceService.listIncoming(tag,Constants.NS_SKOS+"broader");
                List<KiWiTriple> outgoing = resourceService.listOutgoing(tag,Constants.NS_SKOS+"broader");

                if(incoming.size() > 0 || outgoing.size() > 0) {
                    result.add(facadingService.createFacade(tag,Tag.class));
                }
            }


        } catch(ResolvingException ex) {
            log.error("an error occurred while resolving the SKOS namespace; this should not happen and is a programming error.");
        }

        return result;
    }

    /**
     * Get the content items used as tags for the given content item. This method performs an
     * aggregation over all taggings grouped by taggingResource.
     *
     * @param item
     * @return
     */
    @Override
    public List<Tag> getTags(KiWiResource item) {
        Set<KiWiResource> tagCandidates = new HashSet<KiWiResource>();

        try {
            // first look for all directly associated tags
            for(KiWiNode node : resourceService.listOutgoingNodes(item, Constants.NS_HGTAGS+"taggedWithTag")) {

                if(node.isUriResource() || node.isAnonymousResource()) {
                    tagCandidates.add((KiWiResource)node);
                }

             }



            // then list all taggings and look there (needed as long as reasoner is not working properly)
            for(KiWiResource tagging : resourceService.listIncomingNodes(item, Constants.NS_HGTAGS+"taggedResource")) {
                for(KiWiNode tag : resourceService.listOutgoingNodes(tagging,Constants.NS_HGTAGS+"associatedTag")) {
                    if(tag.isUriResource() || tag.isAnonymousResource()) {
                        tagCandidates.add((KiWiResource)tag);
                    }
                }
            }


        } catch(ResolvingException ex) {
            log.error("an error occurred while resolving the SKOS namespace; this should not happen and is a programming error.");
        }


        return new LinkedList<Tag>(facadingService.createFacade(tagCandidates,Tag.class));
     }

    /**
     * Return tags by a given label
     *
     * @param tagLabel
     * @return
     */
    @Override
    public List<Tag> getTagsByLabel(String tagLabel) {
        Collection<KiWiResource> items = resourceService.listResourcesByProperty(Constants.NS_HGTAGS,tagLabel);

        List<Tag> result = new LinkedList<Tag>();

        for(KiWiResource r : items) {
            result.add(facadingService.createFacade(r,Tag.class));
        }

        return result;
    }

    /**
     * /**
     * Counts the current usages of a tag, meaning, how many ContentItems are tagged with the tag.
     *
     * @param resource
     * @return
     * @author szabyg
     */
    @Override
    public Long getTagUsage(KiWiResource resource) {
        throw new UnsupportedOperationException("not implemented");
    }

    /**
     * List all content items tagged with a certain item given as argument. May make use
     * of either database queries or the search service.
     *
     * @param r_tag
     * @return
     */
    @Override
    public Set<KiWiResource> getTaggedItems(KiWiResource r_tag) {
        Tag tag = facadingService.createFacade(r_tag,Tag.class);

        return tag.getTaggedItems();
    }

    /**
     * List the users that are using the content item passed as argument in tagging
     * activities.
     *
     * @param r_tag
     * @return
     */
    @Override
    public Set<KiWiUser> getTagUsers(KiWiResource r_tag) {
        Tag tag = facadingService.createFacade(r_tag,Tag.class);

        Set<Tagging> taggings = tag.getTaggings();

        Set<KiWiUser> result = new HashSet<KiWiUser>();

        for(Tagging t : taggings) {
            result.add(userService.getUserByResource(t.getTaggedBy()));
        }

        return result;
    }


    /**
     * Creeate a new resource that represents a tag and has the specified label- The label will be stored
     * using the hgtags:name and rdfs:label and kiwi:title properties.
     *
     * @param label
     * @return
     */
    @Override
    public Tag createTag(String label) {
        KiWiUriResource r = resourceService.createUriResource();

        return createTag(r,label);
    }

    /**
     * Turn the resource passed as parameter into the tag with the specified label. The label will be stored using
     * the hgtags:name and rdfs:label and kiwi:title properties.
     *
     * @param r
     * @param label
     * @return
     */
    @Override
    public Tag createTag(KiWiResource r, String label) {
        r.setTitle(label);

        Tag t = facadingService.createFacade(r,Tag.class);
        t.setTagLabels(Collections.singleton(label));
        resourceService.setLabel(r, Locale.getDefault(), label);
        try {
            resourceService.setProperty(r,Constants.NS_KIWI_CORE+"title",label);
        } catch (NamespaceResolvingException e) { }


        return t;
    }

    /**
     * Return the tag identified by the given URI, or null if it does not exist.
     *
     * @param uri a URI identifying the resource that represents the tag to be returned
     * @return
     */
    @Override
    public Tag getTag(String uri) {
        KiWiResource r = resourceService.getUriResource(uri);

        if(r == null) {
            return null;
        } else {
            return facadingService.createFacade(r,Tag.class);
        }
    }
}
